package org.test4j.mock.faking.fluent;

import org.test4j.mock.Invocation;
import org.test4j.mock.faking.meta.MethodId;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

/**
 * 自定义mock行为
 *
 * @author darui.wu
 */
public class MockMethod<R> {
    protected final FluentMockUp mockUp;

    private final String realClass;

    private final String name;

    private final String desc;

    private AtomicInteger index = new AtomicInteger(0);

    public MockMethod(FluentMockUp mockUp, String realClass, String name, String desc) {
        this.mockUp = mockUp;
        this.realClass = realClass;
        this.name = name;
        this.desc = desc;
    }

    /**
     * 对方法参数校验
     * <pre>
     *     .assertResult(inv -> {})
     * </pre>
     *
     * @param asserts
     * @return
     */
    public MockMethod<R> assertParameters(Consumer<Invocation> asserts) {
        String methodDesc = MethodId.buildMethodDesc(this.name, this.desc);
        this.mockUp.addAssertMethodParas(methodDesc, asserts);
        this.placeHolder();
        return this;
    }

    /**
     * 对真实调用结果进行断言
     * <pre>
     *     .assertResult((inv,rst) -> {})
     * </pre>
     *
     * @param asserts
     * @return
     */
    public MockMethod<R> assertResult(BiConsumer<Invocation, R> asserts) {
        String methodDesc = MethodId.buildMethodDesc(this.name, this.desc);
        this.mockUp.addAssertMethodResult(methodDesc, asserts);
        this.placeHolder();
        return this;
    }

    /**
     * mock调用依次返回的结果值
     *
     * @param values
     * @return
     */
    public MockMethod<R> thenReturn(R... values) {
        for (Object value : values) {
            this.mockUp.mockReturn(this.realClass, this.name, this.desc, index.incrementAndGet(), value);
        }
        this.mockUp.apply();
        return this;
    }

    /**
     * 指定时序结果
     *
     * @param times  方法调用时序列表, 从1开始计数
     * @param values
     * @return
     */
    public MockMethod<R> thenReturn(int[] times, R... values) {
        int max = 0;
        for (int index = 0; index < times.length; index++) {
            R value = this.getValue(values, index);
            this.mockUp.mockReturn(this.realClass, this.name, this.desc, times[index], value);
            max = Math.max(max, times[index]);
        }
        this.setIncreaseTimes(max);
        this.mockUp.apply();
        return this;
    }

    /**
     * 除已指定的行为外, 剩下的调用都返回value
     *
     * @param value
     */
    public void restReturn(R value) {
        this.mockUp.mockReturn(this.realClass, this.name, this.desc, INDEX_REST_BEHAVIOR, value);
        this.mockUp.apply();
    }

    /**
     * mock调用依次抛出异常
     *
     * @param es
     * @return
     */
    public MockMethod<R> thenThrows(Throwable... es) {
        for (Throwable e : es) {
            this.mockUp.mockThrows(this.realClass, this.name, this.desc, index.incrementAndGet(), e);
        }
        this.mockUp.apply();
        return this;
    }

    public MockMethod<R> thenThrows(int[] times, Throwable... es) {
        int max = 0;
        for (int index = 0; index < times.length; index++) {
            Throwable e = this.getValue(es, index);
            this.mockUp.mockThrows(this.realClass, this.name, this.desc, times[index], e);
            max = Math.max(max, times[index]);
        }
        this.setIncreaseTimes(max);
        this.mockUp.apply();
        return this;
    }

    /**
     * 除已指定的行为外, 剩下的调用都抛出异常
     *
     * @param e
     */
    public void restThrows(Throwable e) {
        this.mockUp.mockThrows(this.realClass, this.name, this.desc, INDEX_REST_BEHAVIOR, e);
        this.mockUp.apply();
    }

    /**
     * mock调用依次执行的逻辑
     *
     * @param fakes
     * @return
     */
    public MockMethod<R> thenAnswer(FakeFunction... fakes) {
        for (FakeFunction fake : fakes) {
            this.mockUp.mockMethod(this.realClass, this.name, this.desc, index.incrementAndGet(), fake);
        }
        this.mockUp.apply();
        return this;
    }

    public MockMethod<R> thenAnswer(int[] times, FakeFunction... fakes) {
        int max = 0;
        for (int index = 0; index < times.length; index++) {
            FakeFunction fake = this.getValue(fakes, index);
            this.mockUp.mockMethod(this.realClass, this.name, this.desc, times[index], fake);
            max = Math.max(max, times[index]);
        }
        this.setIncreaseTimes(max);
        this.mockUp.apply();
        return this;
    }

    /**
     * 除已指定的行为外, 剩下的调用执行的逻辑
     *
     * @param fake
     */
    public void restAnswer(FakeFunction fake) {
        this.mockUp.mockMethod(this.realClass, this.name, this.desc, INDEX_REST_BEHAVIOR, fake);
        this.mockUp.apply();
    }

    /**
     * 默认行为序号
     */
    public final static int INDEX_REST_BEHAVIOR = 0;

    private <A> A getValue(A[] values, int index) {
        if (values == null) {
            return null;
        }
        int size = values.length;
        if (size == 0) {
            return null;
        } else {
            return index < size ? values[index] : values[size - 1];
        }
    }

    /**
     * 按指定时序设置过, 把自增序列设置到最大值
     */
    private void setIncreaseTimes(int max) {
        if (this.index.get() < max) {
            this.index.set(max);
        }
    }

    /**
     * 占位处理, 保证在没有设置模拟行为, 只设置了{@link #assertParameters(Consumer)} 和 {@link #assertResult(BiConsumer)}
     * 时, {@link org.test4j.mock.faking.meta.FakeStates#getLastMethod(MethodId)}可以找到断言
     */
    private void placeHolder() {
        this.mockUp.mockReturn(this.realClass, this.name, this.desc, -1, null);
        this.mockUp.apply();
    }
}